#import <Foundation/Foundation.h>
#import <Carbon/Carbon.h>
#import <AudioToolbox/AudioToolbox.h>

static BOOL g_Verbose = 0;

static void usage( void )
{
    printf( "usage: pcm2aif -pcmFile <inputFile> -aifFile <outputFile> -sampleRate <sampleRate>\n" );
    exit( 0 );
}

static NSData* readPCM( NSString *filename )
{
    return [NSData dataWithContentsOfFile:filename];
}

static int writeAIFF( NSString *fullPath, Float64 sampleRate, NSData *sourceData )
{
    NSString                    *parentDir = [fullPath stringByDeletingLastPathComponent];
    NSString                    *fileName = [fullPath lastPathComponent];
    const char                  *filePath = [fullPath fileSystemRepresentation];
    const char                  *fileSystemPath;
    FSRef                       parentDirRef;
    FSRef                       existingFileRef;
    FSRef                       newFileRef;
    OSStatus                    status;
    AudioFileID                 newFileID;
    AudioStreamBasicDescription sourceFormat;
    AudioStreamBasicDescription destFormat;
    AudioConverterRef           audioConverter;
    
    if ( [parentDir length] == 0 ) {
        // Must be working in the current directory
        parentDir = @"./";
    }
    
    fileSystemPath = [parentDir fileSystemRepresentation];
    
    // If the file already exists, blow it away first.  That way, AudioFileCreate won't fail.
    if ( noErr == FSPathMakeRef( (UInt8*)filePath, &existingFileRef, NULL ) ) {
        FSDeleteObject( &existingFileRef );
    }
    
    if ( FSPathMakeRef( (UInt8*)fileSystemPath, &parentDirRef, NULL ) ) {
        [NSException raise:@"AudioConverterFailure" format:@"FSPathMakeRef failed"];
    }
    
    memset( &sourceFormat, 0, sizeof(AudioStreamBasicDescription) );
    memset( &destFormat, 0, sizeof(AudioStreamBasicDescription) );
    
    sourceFormat.mSampleRate = sampleRate;
    sourceFormat.mFormatID = kAudioFormatLinearPCM;
    sourceFormat.mFormatFlags = ( kAudioFormatFlagIsFloat );
    if ( CFByteOrderGetCurrent() == CFByteOrderBigEndian ) {
        sourceFormat.mFormatFlags |= kAudioFormatFlagIsBigEndian;
    }
    sourceFormat.mBytesPerFrame = sizeof(float);
    sourceFormat.mFramesPerPacket = 1;
    sourceFormat.mBytesPerPacket = sizeof(float) * sourceFormat.mFramesPerPacket;
    sourceFormat.mChannelsPerFrame = 1;
    sourceFormat.mBitsPerChannel = 32;
    
    destFormat.mSampleRate = sampleRate;
    destFormat.mFormatID = kAudioFormatLinearPCM;
    destFormat.mFormatFlags = ( kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagIsBigEndian );
    destFormat.mBytesPerPacket = 2;
    destFormat.mFramesPerPacket = 1;
    destFormat.mBytesPerFrame = 2;
    destFormat.mChannelsPerFrame = 1;
    destFormat.mBitsPerChannel = 16;
    
	status = AudioFileCreate( &parentDirRef, (CFStringRef)fileName, kAudioFileAIFFType, &destFormat, 0, &newFileRef, &newFileID);
    if ( noErr != status ) {
        [NSException raise:@"AudioConverterFailure" format:@"AudioFileCreate failed (status=%d)", status];
    }
    
    status = AudioConverterNew( &sourceFormat, &destFormat, &audioConverter );
    if ( noErr != status ) {
        [NSException raise:@"AudioConverterFailure" format:@"AudioConverterNew failed (status=%d)", status];
    }
    
    unsigned int	length = [sourceData length] / sizeof( float );
    NSMutableData   *outData = [NSMutableData dataWithLength:(length * destFormat.mBytesPerFrame)];
    void            *outBytes = [outData mutableBytes];
    UInt32          outBytesLength = [outData length];
    const void      *inBytes = [sourceData bytes];
    UInt32          inBytesLength = length * sourceFormat.mBytesPerFrame;
    
    status = AudioConverterConvertBuffer( audioConverter, inBytesLength, (void*)inBytes, &outBytesLength, outBytes );
    if ( noErr != status ) {
        [NSException raise:@"AudioConverterFailure" format:@"AudioConverterConvertBuffer failed (status=%d)", status];
    }
    
    status = AudioFileWriteBytes( newFileID, 0, 0, &outBytesLength, outBytes );
    if ( noErr != status ) {
        [NSException raise:@"AudioConverterFailure" format:@"AudioFileWriteBytes failed (status=%d)", status];
    }
    
    status = AudioConverterDispose( audioConverter );
    if ( noErr != status ) {
        [NSException raise:@"AudioConverterFailure" format:@"AudioConverterDispose failed (status=%d)", status];
    }            
    
    status = AudioFileClose( newFileID );
    if ( noErr != status ) {
        [NSException raise:@"AudioConverterFailure" format:@"AudioFileClose failed (status=%d)", status];
    }
	
	return 0;
    
}

int main( int argc, char *argv[] ) 
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSUserDefaults *args = [NSUserDefaults standardUserDefaults];

    NSString *pcmFilename = [args stringForKey:@"pcmFile"];
    NSString *aifFilename = [args stringForKey:@"aifFile"];
    Float64 sampleRate = [args floatForKey:@"sampleRate"];
    g_Verbose = [args boolForKey:@"verbose"];

    if ( !pcmFilename || !aifFilename || ( sampleRate == 0.0 ) ) {
        usage();
    }
    
    pcmFilename = [pcmFilename stringByExpandingTildeInPath];
    aifFilename = [aifFilename stringByExpandingTildeInPath];
    
    NSFileManager *fm = [NSFileManager defaultManager];
    if ( ![fm fileExistsAtPath:pcmFilename] ) {
        printf( "PCM file does not exist.\n" );
        exit(0);
    }

    // Read the binary data from the pcm file - it's just a collection of floats
    NSData *pcmData = readPCM( pcmFilename );
    if ( !pcmData ) {
        printf( "Could not read PCM data.\n" );
        exit(0);
    }
    
    if ( [fm fileExistsAtPath:aifFilename] ) {
        printf( "Overwriting existing AIF file.\n" );
    }

    // Write the data out to an aiff file
    writeAIFF( aifFilename, sampleRate, pcmData );

    [pool release];
    return 0;
}